package montecarloMultithread;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;




public class MultiThreadMontCarlo
{
		//variables with default values
		private	double currentPrice=40;
		private	double strikePrice=50;
		private	double maturityTime=0.5;
		private	double discountRate=0.06;
		private	double carryRate=0.1;
		private	double volatility=0.45;
		private	int numSteps=168;
		private	int numSimulations=100000;
		private	double z=1;
		
		//for muti-threading
		private int poolSize= (int)(Runtime.getRuntime().availableProcessors()/ (1 - 0.75));

		
		//Montecarlo function
		public double montecarlo() throws ExecutionException, InterruptedException{
			
		//debug
			System.out.println("PoolSize= "+ poolSize);
			
			
			
			double dt;
			double Sum=0;
			double Drift,vSqrdt;
			
			//initialization variables
			dt=maturityTime/numSteps;
			Drift=(carryRate-volatility*volatility/2)*dt;
			vSqrdt=volatility*Math.sqrt(dt);
			
							
		    		       ExecutorService executorPool = Executors.newFixedThreadPool(poolSize);
		    		      CompletionService<Double> Pool = new ExecutorCompletionService<Double>(executorPool);
		    		      
		    		     List <Future<Double>> resultsPool=new ArrayList <Future<Double>>(numSimulations);
		    		     for(int i=1;i<=numSimulations;i++) {
		    		    	  Future<Double> temp=Pool.submit(new MyCallable(Drift,vSqrdt));
		    		    	  resultsPool.add(temp);
		    				}
		    		      
		    		      executorPool.shutdown();
		    
		    		      //get values
		    		       for(Future<Double> list : resultsPool){
		    		    	   Sum=Sum+Math.max(z*(list.get()-strikePrice), 0);
		    		       }
		    
		
			return Math.exp(-discountRate*maturityTime)*(Sum/numSimulations);
		}
		

		//constructors
		public MultiThreadMontCarlo(){}
		public MultiThreadMontCarlo(double Coef){
			//multi-threading parameters
			double blockingCoefficient=Coef;
			poolSize= (int)(Runtime.getRuntime().availableProcessors()/ (1 - blockingCoefficient));
		}
		public MultiThreadMontCarlo(String CallPutFlag, double currentPrice, double strikePrice, 
				double maturityTime,double discountRate,double carryRate,double volatility, int numSteps,
				int numSimulations){
			//montecarlo parameters
			this.currentPrice=currentPrice;
			this.strikePrice=strikePrice;
			this.discountRate=discountRate;
			this.maturityTime=maturityTime;
			this.carryRate=carryRate;
			this.volatility=volatility;
			this.numSteps=numSteps;
			this.numSimulations=numSimulations;
			
			if(CallPutFlag.contains("c")){
				this.z=1;
			}
			else if(CallPutFlag.contains("p")){
				this.z=-1;
			}
			else{
				this.z=0;
			}
			
		}
		
		public MultiThreadMontCarlo(String CallPutFlag, double currentPrice, double strikePrice, 
				double maturityTime,double discountRate,double carryRate,double volatility, int numSteps,
				int numSimulations,double Coef){
			//montecarlo parameters
			this.currentPrice=currentPrice;
			this.strikePrice=strikePrice;
			this.discountRate=discountRate;
			this.maturityTime=maturityTime;
			this.carryRate=carryRate;
			this.volatility=volatility;
			this.numSteps=numSteps;
			this.numSimulations=numSimulations;
			if(CallPutFlag.contains("c")){
				this.z=1;
			}
			else if(CallPutFlag.contains("p")){
				this.z=-1;
			}
			else{
				this.z=0;
			}
			
			//multi-threading parameters
			double blockingCoefficient=Coef;
			poolSize= (int)(Runtime.getRuntime().availableProcessors()/ (1 - blockingCoefficient));
		}
		
		public int getPoolSize() {
			return poolSize;
		}
		
		
		/////////////////////////////////////////////////////////
		//			Callable process (thread) with return	   //
		/////////////////////////////////////////////////////////
		
		//use for the montecarlo algorithm
		private class MyCallable 
		implements Callable<Double>{
			 private double Drift;
	    	 private double vSqrdt;
	    	  
	    	  MyCallable(double Drift, double vSqrdt){
	    		  this.Drift=Drift;
	    		  this.vSqrdt=vSqrdt;
	    	  }
	    	  
	          public Double call() throws Exception {
	        	  double St=currentPrice;  
	        	  
	        	//initialize random
	        	  Random rand = new Random();
	  			//calculation
					for(int j=1;j<=numSteps;j++){
						St=St*Math.exp(Drift+vSqrdt*rand.nextGaussian());
					}
					return St;
	            }  
		}
	
}
